# -*- coding: utf-8 -*-
# @Author  : pony.guan
# @Time    : 2021/10/18
# Description:

import os
import time
import json
import logging
import shutil
import base64

from django.http import JsonResponse, FileResponse
from django.http import StreamingHttpResponse
from django.utils.encoding import escape_uri_path
from rest_framework.viewsets import ModelViewSet
from rest_framework.decorators import action
from dwebsocket.decorators import accept_websocket
from django.db import transaction
from django.conf import settings

from apps.ws_app.models import ProjectMsg, Gui
from apps.ws_app.serializer import ProjectMsgSerializer, GuiSerializer

logger = logging.getLogger('django')


class MyModelViewSet(ModelViewSet):
    def list(self, request, *args, **kwargs):
        res = super().list(request, *args, **kwargs)
        return JsonResponse({
            'code': 1000,
            'data': res.data
        })

    def create(self, request, *args, **kwargs):
        res = super().create(request, *args, **kwargs)
        return JsonResponse({
            'code': 1000,
            'data': res.data
        })
        # return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def destroy(self, request, *args, **kwargs):
        super().destroy(request, *args, **kwargs)
        return JsonResponse({
            'code': 1000,
            'data': '删除成功'
        })

    def update(self, request, *args, **kwargs):
        res = super().update(request, *args, **kwargs)
        return JsonResponse({
            'code': 1000,
            'data': res.data
        })


class ProjectMsgView(MyModelViewSet):
    queryset = ProjectMsg.objects.all()
    serializer_class = ProjectMsgSerializer

    @action(methods=['get'], detail=False)
    def station_type(self, request):
        """
        工作站类型
        :return:
        """
        station_types = settings.STATION_TYPES
        return JsonResponse({
            'code': 1000,
            'data': station_types
        })

    @action(methods=['get'], detail=False)
    def get_station(self, request):

        project_name, station_type = request.query_params['project_name'], request.query_params['station_type']
        if station_type == 'ROBOT':
            available_stations_field = 'available_robot_stations'
            assigned_stations_field = 'assigned_robot_stations'
        elif station_type == 'CONVEYOR':
            available_stations_field = 'available_conveyor_stations'
            assigned_stations_field = 'assigned_conveyor_stations'
        else:
            return JsonResponse({
                'code': 1001,
                'error': f'不支持的工作站类型: {station_type}'
            })
        # 利用悲观锁实现并发控制
        with transaction.atomic():
            obj = ProjectMsg.objects.select_for_update().filter(project_name=project_name).first()  # 悲观锁
            if not obj:
                return JsonResponse({
                    'code': 1001,
                    'error': f'数据库中没有项目:{project_name}'
                })
            # available_stations = obj.available_stations.split(',')
            # assigned_stations = obj.assigned_stations.split(',') if obj.assigned_stations else []

            available_stations = getattr(obj, available_stations_field).split(',') \
                if getattr(obj, available_stations_field) else []
            assigned_stations = getattr(obj, assigned_stations_field).split(',') \
                if getattr(obj, assigned_stations_field) else []

            if not available_stations:
                return JsonResponse({
                    'code': 1001,
                    'error': f'演示系统中没有可用的{station_type}工作站了'
                })
            mysql_host = obj.mysql_host
            # 从项目可用的工作站中筛选出离线工作站
            with settings.CURSOR_CONTEXT(mysql_host) as sc:
                sql = "select station_code from ks_station where station_code in %s and " \
                      "station_physical_type=%s and station_status='OFFLINE'"
                sc.execute(sql, (available_stations, station_type))
                rows = sc.fetchall()
            if rows:
                # 离线且可用的工作站
                off_line_available_stations = [i['station_code'] for i in rows]
                # 离线且可用且没有分配给用户的工作站
                off_line_available_not_assign_stations = list(set(off_line_available_stations) - set(assigned_stations))
                if not off_line_available_not_assign_stations:
                    return JsonResponse({
                        'code': 1001,
                        'error': f'没有空闲的{station_type}工作站，请稍后再试'
                    })

                else:
                    assign_station = off_line_available_not_assign_stations[0]
                    setattr(obj, assigned_stations_field, ','.join(assigned_stations + [assign_station]))
                    obj.save()
                    logger.info(f'已经分配工作站：项目-{project_name}, 工作站类型-{station_type}， 工作站-{assign_station}')
                    return JsonResponse({
                        'code': 1000,
                        'data': {
                            'project_name': project_name,
                            'assign_station': assign_station,
                            'station_type': station_type
                        }
                    })
            else:
                return JsonResponse({
                    'code': 1001,
                    'error': '没有离线且可用的工作站，请稍后再试'
                })

    @action(methods=['patch'], detail=False)
    def release_station(self, request):
        """
        释放工作站
        :param request:
        :return:
        """
        project_name, release_station, station_type = request.data['project_name'], request.data['release_station'], \
                                                      request.data['station_type']
        if station_type == 'ROBOT':
            assigned_stations_field = 'assigned_robot_stations'
        elif station_type == 'CONVEYOR':
            assigned_stations_field = 'assigned_conveyor_stations'
        else:
            return JsonResponse({
                'code': 1001,
                'error': f'不支持的工作站类型: {station_type}'
            })
        obj = self.get_queryset().filter(project_name=project_name).first()
        assigned_stations = getattr(obj, assigned_stations_field).split(',')
        if release_station not in assigned_stations:
            return JsonResponse({
                'code': 1001,
                'error': f'该工作站{release_station}不是项目{project_name}分配的'
            })
        else:
            assigned_stations.remove(release_station)
        setattr(obj, assigned_stations_field, ','.join(assigned_stations))
        obj.save()
        logger.info(f'已经释放工作站：项目-{project_name}, 工作站类型-{station_type}， 工作站-{release_station}')
        return JsonResponse({
            'code': 1000,
            'data': f'{project_name}项目的{release_station}号工作站释放成功'
        })


class GuiView(MyModelViewSet):
    queryset = Gui.objects.all()
    serializer_class = GuiSerializer

    def create(self, request, *args, **kwargs):
        json_response = super().create(request, *args, **kwargs)
        file = request.FILES.get('file')
        upload_dir = os.path.join(settings.BASE_DIR, 'upload')
        if os.path.exists(upload_dir):
            shutil.rmtree(upload_dir)
        os.mkdir(upload_dir)
        with open(os.path.join(upload_dir, file.name), 'wb+') as f:
            for chunk in file.chunks():
                f.write(chunk)
        return json_response


def get_channels(request):
    """
    创建操作台，并返回操作台和频道
    :param request:
    :return:
    """
    ip = os.environ.get('IP')
    redis_port = os.environ.get('REDIS_PORT', 16379)
    backend_port = os.environ.get('BACKEND_PORT', 8008)
    project_name = request.GET['project_name']
    station_type = request.GET['station_type']
    get_station_url = f'http://{ip}:{backend_port}/api/v1/system_demo/ws_app/project_msg/get_station/'
    release_station_url = f'http://{ip}:{backend_port}/api/v1/system_demo/ws_app/project_msg/release_station/'
    channel = str(int(time.time() * 1000))
    data = {
        'project_name': project_name,
        'ip': ip,
        'redis_port': redis_port,
        'channel': channel,
        'station_type': station_type,
        'get_station_url': get_station_url,
        'release_station_url': release_station_url
    }
    logger.info(f'响应get_channels:{json.dumps(data, ensure_ascii=False)}')
    # 加密data
    data = json.dumps(data)
    data_byte = data.encode('utf-8')
    base_data = base64.b64encode(data_byte)
    data_str = base_data.decode('utf-8')

    return JsonResponse({
        'code': 1000,
        'data': data_str
    })


@accept_websocket
def ws_test(request):
    if request.is_websocket():
        logger.info('建立ws_test连接成功，等待消息....')
        msg_ = request.websocket.wait()
        to_server = 'to_server_test'
        obj = settings.REDIS_PUB_SUB(channel_name=to_server)
        redis_sub = obj.subscribe()
        logger.info(f'开始监听频道：{to_server}')
        msg = redis_sub.listen()
        for i in msg:
            if i["type"] == "message":
                msg_ = str(i["data"], encoding="utf-8")
                msg_ = json.loads(msg_)
                request.websocket.send(json.dumps({
                    'code': 1000,
                    'data': msg_
                }))
                time.sleep(1)  # 确保消息在ws连接关闭前发出去
                logger.info(f'监听到频道:{to_server} 的消息: {msg_}, 并推送到页面')
                if msg_['title'] == '完成':
                    logger.info(f'监听频道: {to_server} 结束')
                    request.websocket.close()
                    redis_sub.unsubscribe(to_server)


@accept_websocket
def task_flow(request):
    if request.is_websocket():
        logger.info('建立ws连接成功，等待消息....')
        msg_ = request.websocket.wait()  # 接收前端发来的消息
        msg_ = json.loads(msg_)
        data_str = msg_['to_gui'][7:]
        data_byte = base64.b64decode(data_str)
        data_str = data_byte.decode('utf-8')
        data = json.loads(data_str)
        task_type = msg_['task_type']
        to_gui, to_server = 'to_gui_' + data['channel'], 'to_server_' + data['channel']
        logger.info(f'已收到消息: {data_str}')
        # 发布消息到to_gui频道（发布消息到gui客户端）
        obj = settings.REDIS_PUB_SUB(channel_name=to_gui)
        obj.public(json.dumps({'task_type': task_type}))
        logger.info(f'已将任务: {task_type}　发布到频道: {to_gui}')
        # 监听to_server频道(gui客户端传来的消息)
        obj = settings.REDIS_PUB_SUB(channel_name=to_server)
        redis_sub = obj.subscribe()
        logger.info(f'开始监听频道：{to_server}')
        msg = redis_sub.listen()
        for i in msg:
            if i["type"] == "message":
                msg_ = str(i["data"], encoding="utf-8")
                msg_ = json.loads(msg_)
                if 'error' in msg_:
                    request.websocket.send(json.dumps(msg_))
                    time.sleep(1)
                    logger.info(f'收到了error:{msg_},监听频道: {to_server} 结束')
                    request.websocket.close()
                    redis_sub.unsubscribe(to_server)
                else:
                    request.websocket.send(json.dumps({
                        'code': 1000,
                        'data': msg_
                    }))
                time.sleep(1)  # 确保消息在ws连接关闭前发出去
                logger.info(f'监听到频道:{to_server} 的消息: {msg_}, 并推送到页面')
                if msg_['title'] == '完成':
                    logger.info(f'监听频道: {to_server} 结束')
                    request.websocket.close()
                    redis_sub.unsubscribe(to_server)


# def upload_gui(request):
#     """
#     @desc 上传gui
#     :param request:
#     :return:
#     """
#     if request.method == 'POST':
#         file = request.FILES.get('file')
#         # update_msg = request.POST.get('update_msg')
#         # with settings.REDIS_CONNECT() as r_conn:
#         #     r_conn.set('update_msg', update_msg)
#         upload_dir = os.path.join(settings.BASE_DIR, 'upload')
#         if os.path.exists(upload_dir):
#             shutil.rmtree(upload_dir)
#         os.mkdir(upload_dir)
#         with open(os.path.join(upload_dir, file.name), 'wb+') as f:
#             for chunk in file.chunks():
#                 f.write(chunk)
#
#         return JsonResponse({
#             'code': 1000,
#             'data': '上传成功'
#         })


def download_gui(request):
    """
    @desc 下载gui
    :param request:
    :return:
    """
    upload_dir = os.path.join(settings.BASE_DIR, 'upload')
    gui_name = os.listdir(upload_dir)[0]
    gui_path = os.path.join(upload_dir, gui_name)

    def file_iterator(file_name, chunk_size=512):
        with open(file_name, 'rb') as f:
            while True:
                c = f.read(chunk_size)
                if c:
                    yield c
                else:
                    break

    if os.path.exists(gui_path):
        response = StreamingHttpResponse(file_iterator(gui_path))
        response['Content-Type'] = 'application/octet-stream'
        response['Content-Disposition'] = 'attachment;filename="{0}"'.format(escape_uri_path(gui_name))
        return response
    else:
        return JsonResponse({
            'code': 1001,
            'error': '文件不存在'
        })

# ws = new WebSocket('ws://172.20.3.222:8008/api/v1/system_demo/ws_app/task_flow/')
# ws.onmessage = event => console.log(event.data)
# ws.send('hello')
